﻿#include "parse.h"

#include "tars.tab.hpp"
#include <errno.h>
#include <fstream>
#include <string.h>
#include "util/tc_common.h"
#include "util/tc_file.h"

using namespace std;

TarsParsePtr g_parse = new TarsParse();

void yyerror(char const* msg) {
    g_parse->error(msg);
}

TarsParse::TarsParse() {
    _bWithTars = false;
    _bUseCurrentPath = false;
    _itab = 0;
    _bUseCurrentPathFirst = false;
    initScanner();
}

void TarsParse::setTars(bool bWithTars) {
    _bWithTars = bWithTars;
}

void TarsParse::setHeader(const string& sHeader) {
    _sHeader = sHeader;
}

void TarsParse::setCurrentPriority(bool bFlag) {
    _bUseCurrentPathFirst = bFlag;
}

string TarsParse::getHeader() {
    if (_sHeader.empty()) {
        return _sHeader;
    }
    return _sHeader + "/";
}

void TarsParse::clear() {
    while (!_contexts.empty()) {
        _contexts.pop();
    }
    while (!_contains.empty()) {
        _contains.pop();
    }
    _vcontexts.clear();
    _structs.clear();
    _enums.clear();
    _namespaces.clear();
}

void TarsParse::parse(const string& sFileName) {
    if (_bUseCurrentPath) {
        auto sTemp = sFileName;
        auto isExist = tars::TC_File::isFileExist(sFileName);
        if (!isExist) {
            sTemp = tars::TC_File::extractFileName(sFileName);
        }
        clear();

        _contains.push(new Container(""));
        if (!(yyin = fopen(sTemp.c_str(), "r"))) {
            error("open file '" + sFileName + "(" + sTemp + ")" + "' error :" + string(strerror(errno)));
        }
        pushFile(sFileName);
        yyparse();
        return;
    }

    if (_bUseCurrentPathFirst) {
        auto sTemp = tars::TC_File::extractFileName(sFileName);
        auto isExist = tars::TC_File::isFileExist(sTemp);
        if (!isExist) {
            sTemp = sFileName;
        }
        clear();

        _contains.push(new Container(""));
        if (!(yyin = fopen(sTemp.c_str(), "r"))) {
            error("open file '" + sFileName + "(" + sTemp + ")" + "' error :" + string(strerror(errno)));
        }
        pushFile(sFileName);
        yyparse();
        return ;
    }
    {
        clear();
        _contains.push(new Container(""));
        if (!(yyin = fopen(sFileName.c_str(), "r"))) {
            error("open file '" + sFileName + "' error :" + string(strerror(errno)));
        }
        pushFile(sFileName);
        yyparse();
    }
}

void TarsParse::pushFile(const string& file) {
    auto c = new Context(file);
    _contexts.push(c);
    _vcontexts.push_back(c);
}

ContextPtr TarsParse::popFile() {
    auto c = _contexts.top();
    _contexts.pop();
    return c;
}

bool TarsParse::getFilePath(const string& s, string& file) {
    if (_bUseCurrentPath) {
        if (s.length() < 2 || s[0] != '\"' || s[s.length() - 1] != '\"') {
            error("#include need \"FILENAME\"");
        }
        file = s.substr(1, s.length() - 2);
        auto sTemp = file;
        auto isExist = tars::TC_File::isFileExist(file);
        if (!isExist) {
            file = tars::TC_File::extractFileName(file);
        }
        for (size_t i = 0; i < _vcontexts.size(); i++) {
            if (_vcontexts[i]->getFileName() == file) {
                return false;
            }
        }
        return true;
    }

    if (_bUseCurrentPathFirst) {
        if (s.length() < 2 || s[0] != '\"' || s[s.length() - 1] != '\"') {
            error("#include need \"FILENAME\"");
        }
        file = s.substr(1, s.length() - 2);
        auto sTemp = tars::TC_File::extractFileName(file);
        auto isExist = tars::TC_File::isFileExist(sTemp);
        if (isExist) {
            file = sTemp;
        }
        for (size_t i = 0; i < _vcontexts.size(); i++) {
            if (_vcontexts[i]->getFileName() == file) {
                return false;
            }
        }
        return true;
    }

    if (s.length() < 2 || s[0] != '\"' || s[s.length() - 1] != '\"') {
        error("#include need \"FILENAME\"");
    }

    file = s.substr(1, s.length() - 2);
    if (!tars::TC_File::isFileExist(file)) {
        for (size_t i = 0; i < _vIncludePath.size(); i++) {
            if (tars::TC_File::isFileExist(_vIncludePath[i] + "/" + file)) {
                file = _vIncludePath[i] + "/" + file;
                break;
            }
        }
    }
    for (size_t i = 0; i < _vcontexts.size(); i++) {
        if (_vcontexts[i]->getFileName() == file) {
            return false;
        }
    }
    return true;
}

string TarsParse::getCurrFileName() {
    return _contexts.top()->getFileName();
}

void TarsParse::nextLine() {
    _contexts.top()->nextLine();
}

ContextPtr TarsParse::currentContextPtr() {
    return _contexts.top();
}

void TarsParse::error(const string& msg) {
    cerr <<  _contexts.top()->getFileName() << ": " << _contexts.top()->getCurrLine() << ": error: " << msg << endl;
    exit(-1);
}

int TarsParse::checkKeyword(const string& s) {
    auto it = _keywordMap.find(s);
    if (it != _keywordMap.end()) {
        return it->second;
    }
    if (!_bWithTars) {
        string sPrefix = "tars";
        //不能以tars开头
        if ((s.length() >= sPrefix.length()) && (s.substr(0, sPrefix.length()) == sPrefix)) {
            error("identifier can't start with 'tars'");
        }
    }
    return TARS_IDENTIFIER;
}

void TarsParse::initScanner() {
    _keywordMap["void"]     = TARS_VOID;
    _keywordMap["struct"]   = TARS_STRUCT;
    _keywordMap["bool"]     = TARS_BOOL;
    _keywordMap["byte"]     = TARS_BYTE;
    _keywordMap["short"]    = TARS_SHORT;
    _keywordMap["int"]      = TARS_INT;
    _keywordMap["double"]   = TARS_DOUBLE;
    _keywordMap["float"]    = TARS_FLOAT;
    _keywordMap["long"]     = TARS_LONG;
    _keywordMap["string"]   = TARS_STRING;
    _keywordMap["vector"]   = TARS_VECTOR;
    _keywordMap["map"]      = TARS_MAP;
    _keywordMap["key"]      = TARS_KEY;
    _keywordMap["routekey"] = TARS_ROUTE_KEY;
    _keywordMap["module"]   = TARS_NAMESPACE;
    _keywordMap["interface"] = TARS_INTERFACE;
    _keywordMap["out"]      = TARS_OUT;
    _keywordMap["require"]  = TARS_REQUIRE;
    _keywordMap["optional"] = TARS_OPTIONAL;
    _keywordMap["false"]    = TARS_FALSE;
    _keywordMap["true"]     = TARS_TRUE;
    _keywordMap["enum"]     = TARS_ENUM;
    _keywordMap["const"]    = TARS_CONST;
    _keywordMap["unsigned"] = TARS_UNSIGNED;
}

string TarsParse::getTab() {
    ostringstream s;
    for (int i = 0; i < _itab; i++) {
        s << "    ";
    }
    return s.str();
}

BuiltinPtr TarsParse::createBuiltin(Builtin::Kind kind, bool isUnsigned) {
    return new Builtin(kind, isUnsigned);
}

VectorPtr TarsParse::createVector(const TypePtr& ptr) {
    return new Vector(ptr);
}

MapPtr TarsParse::createMap(const TypePtr& pleft, const TypePtr& pright) {
    return new Map(pleft, pright);
}

void TarsParse::addNamespacePtr(const NamespacePtr& nPtr) {
    _namespaces.push_back(nPtr);
}

NamespacePtr TarsParse::findNamespace(const string& id) {
    for (size_t i = 0; i < _namespaces.size(); i++) {
        if (_namespaces[i]->getId() == id) {
            return _namespaces[i];
        }
    }
    return nullptr;
}

NamespacePtr TarsParse::currentNamespace() {
    return _namespaces.back();
}

void TarsParse::addStructPtr(const StructPtr& sPtr) {
    _structs.push_back(sPtr);
}

void TarsParse::addEnumPtr(const EnumPtr& ePtr) {
    _enums.push_back(ePtr);
}

StructPtr TarsParse::findStruct(const string& sid) {
    auto ssid = sid;
    //在当前namespace中查找
    auto np = currentNamespace();
    if (ssid.find("::") == string::npos) {
        ssid = np->getId() + "::" + ssid;
    }
    for (size_t i = 0; i < _structs.size(); i++) {
        if (_structs[i]->getSid() == ssid) {
            return _structs[i];
        }
    }
    return nullptr;
}

EnumPtr TarsParse::findEnum(const string& sid) {
    auto ssid = sid;
    //在当前namespace中查找
    auto np = currentNamespace();
    if (ssid.find("::") == string::npos) {
        ssid = np->getId() + "::" + sid;
    }
    for (size_t i = 0; i < _enums.size(); i++) {
        if (_enums[i]->getSid() == ssid) {
            return _enums[i];
        }
    }
    return nullptr;
}

bool TarsParse::checkEnum(const string& idName) {
    for (size_t i = 0; i < _enums.size(); i++) {
        auto& list = _enums[i]->getAllMemberPtr();
        for (size_t j = 0; j < list.size(); j++) {
            if (list[j]->getId() == idName) {
                return true;
            }
        }
    }
    return false;
}
void TarsParse::checkConflict(const string& sid) {
    //是否和枚举重名
    if (findEnum(sid)) {
        error("conflicts with enum '" + sid + "'");
    }
    //是否和结构重名
    if (findStruct(sid)) {
        error("conflicts with struct '" + sid + "'");
    }
}

TypePtr TarsParse::findUserType(const string& sid) {
    auto sPtr = findStruct(sid);
    if (sPtr) {
        return sPtr;
    }
    auto ePtr = findEnum(sid);
    if (ePtr) {
        return ePtr;
    }
    return nullptr;
}

ContainerPtr TarsParse::currentContainer() {
    return _contains.top();
}

void TarsParse::pushContainer(const ContainerPtr& c) {
    _contains.push(c);
    auto np = NamespacePtr::dynamicCast(c);
    if (np) {
        addNamespacePtr(np);
    }
}

ContainerPtr TarsParse::popContainer() {
    auto c = _contains.top();
    _contains.pop();
    return c;
}

void TarsParse::checkTag(int i) {
    if (i >= 256) {
        error("struct memeber tag can't beyond 256");
    }
    if (i < 0) {
        error("struct memeber tag can't less then 0");
    }
}

void TarsParse::checkSize(int i) {
    if (i >= 1024 * 1024) {
        error("struct memeber size can't beyond 1M");
    }
    if (i < 1) {
        error("struct memeber size can't less than 1");
    }
}

void TarsParse::checkArrayVaid(const TypePtr& tPtr, int size) {
    checkSize(size);
    //只有string/vector可以为array类型
    //vector不可以嵌套array类型 例如不允许vector<string:8>:2;
    auto vPtr = VectorPtr::dynamicCast(tPtr);
    if (vPtr) {
        if (vPtr->getTypePtr()->isArray()) {
            error("fixed array type can not be nested");
        }
        return;
    }
    auto bPtr  = BuiltinPtr::dynamicCast(tPtr);
    if (bPtr && bPtr->kind() == Builtin::KindString) {
        return;
    }
    error("only string or vector can use fix array");
}

void TarsParse::checkPointerVaid(const TypePtr& tPtr) {
    //必须为vector<Byte>类型
    auto vPtr = VectorPtr::dynamicCast(tPtr);
    if (vPtr) {
        auto bPtr  = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
        if (bPtr && bPtr->kind() == Builtin::KindByte) {
            return;
        }
    }
    error("only 'byte *' can  be pointer type");
}

void TarsParse::checkConstValue(const TypeIdPtr& tPtr, int c) {
    //只有内建类型才能有缺省值
    auto bPtr = BuiltinPtr::dynamicCast(tPtr->getTypePtr());
    auto ePtr = EnumPtr::dynamicCast(tPtr->getTypePtr());
    if (!bPtr && !ePtr) {
        error("only base/enum type can have default value");
    }
    if (ePtr) {
        if (c != ConstGrammar::VALUE && c != ConstGrammar::ENUM) {
            error("default value of enum only be int or enum_type");
        }
        return ;
    }

    auto b = bPtr->kind();
    if (c == ConstGrammar::VALUE) {
        if (b == Builtin::KindBool) {
            error("default value of bool can only be true or false");
        }
        if (b == Builtin::KindString) {
            error("default value of string can only be \"string\"");
        }
    } else if (c == ConstGrammar::BOOL) {
        if (b != Builtin::KindBool) {
            error("only bool type can be true or false");
        }
    } else if (c == ConstGrammar::STRING) {
        if (b != Builtin::KindString) {
            error("only string type can be \"string\"");
        }
    }
}

string TarsParse::printHeaderRemark() {
    ostringstream s;
    s << "// **********************************************************************" << endl;
    s << "// This file was generated by a TARS parser!" << endl;
    s << "// TARS version " << TARS_VERSION << "." << endl;
    s << "// **********************************************************************" << endl;
    s << endl;
    return s.str();
}

string TarsParse::getFileName(const string& fileName) {
    auto tmpFileName = fileName;
    auto pos = tmpFileName.rfind('/');
    if (pos != string::npos) {
        tmpFileName = tmpFileName.substr(pos + 1);
    } else {
        pos = tmpFileName.rfind('\\');
        if (pos != string::npos) {
            tmpFileName = tmpFileName.substr(pos + 1);
        }
    }
    return tars::TC_File::excludeFileExt(tmpFileName);
}

string TarsParse::replaceFileName(const string& fileName, const string& ext) {
    return tars::TC_File::excludeFileExt(getFileName(fileName)) + "." + ext;
}

string TarsParse::getAbsoluteFileName(const string& baseDir, const string& fileName) {
    return baseDir + FILE_SEP + fileName;
}
